#include <ne.h>
#include <system/GlutApplication.h>

int Width;
int Height;

int lineMode = 0;
int alphaMode = 0;
int lightOn = 0;
bool playAnimation = false;
ne::Vector2 mousePosition;
ne::real const JointRadius = 0.02f;
bool mouseOn = false;
ne::Engine *pEngine = 0;
ne::Light *light = 0;
ne::Timer timer;
ne::Camera *camera = 0;
ne::Viewport *viewport = 0;
ne::GlutApplication *pApplication = 0;
ne::SkeletonPtr pSkeleton;
ne::AnimationPtr pAnimation;
ne::Timer animationTimer;
//ne::GLSLProgramPtr pProgram;

void keyboard(unsigned char key, int x, int y);
void keyboardUp(unsigned char key, int x, int y);
void mouseFunc(int button, int state, int x, int y);
void motionFunc(int x, int y);
void loop(void);
void redraw(void);
void drawFPS(void);
void resize(int width, int height);
void init();

int main(int argc, char *argv[]) /* I - Command-line arguments */
{
    pApplication = new ne::GlutApplication();//(argc, argv);
    glutReshapeFunc(resize);
    glutDisplayFunc(loop);
    glutKeyboardFunc(keyboard);
    glutMouseFunc(mouseFunc);
    glutMotionFunc(motionFunc);

    pEngine = pApplication->engine;
    if (!pEngine || !pEngine->isReady())
    {
        delete pApplication;
        return 0;
    }

    // Create Mesh
    if (argc > 1)
    {
        ne::SkeletonManager *pSkeletonManager = pEngine->getSkeletonManager();
        pSkeleton = pSkeletonManager->create("TestSkeleton", argv[1]);
        pSkeleton->load();

        if (argc > 2)
        {
            ne::AnimationManager *pAnimationManager = pEngine->getAnimationManager();
            ne::AnimationGroupPtr pAnimationGroup = pAnimationManager->create("TestAnimationGroup", argv[2]);
            pAnimation = pAnimationGroup->create("TestAnimation", argv[2]);
            pAnimation->load();
        }
    }

    init();

    glutMainLoop();

    delete pApplication;

    return 0;
}

/*
void setLineMode(ne::UIButton *button)
{
    lineMode ^= 1;
    if (lineMode)
    {
        button->setText("Fill Mode");
    }
    else
    {
        button->setText("Line Mode");
    }
}

void setAlphaMode(ne::UIButton *button)
{
    alphaMode ^= 1;
    if (alphaMode)
    {
        button->setText("Color Mode");
    }
    else
    {
        button->setText("Alpha Mode");
    }
}

void setLightOn(ne::UIButton *button)
{
    lightOn ^= 1;
    if (lightOn)
    {
        button->setText("Light Off");
    }
    else
    {
        button->setText("Light On");
    }
}
*/

void init()
{
    camera = new ne::Camera();
    viewport = new ne::Viewport(camera);
    camera->setPosition(ne::Vector3::UNIT_Z * 100);

    glClearColor(0, 0, 0, 0);
    glClearDepth(1);
    glEnable(GL_BLEND);
    glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);

    glDepthFunc(GL_LEQUAL);
    glEnable(GL_DEPTH_TEST);

    glCullFace(GL_BACK);
    glEnable(GL_CULL_FACE);

    //glEnable(GL_COLOR_MATERIAL);
    //glColorMaterial(GL_FRONT_AND_BACK, GL_AMBIENT_AND_DIFFUSE);
    glDisable(GL_LIGHTING);

    glShadeModel(GL_SMOOTH);
    glEnable(GL_POINT_SMOOTH);
    glHint(GL_POINT_SMOOTH_HINT, GL_NICEST);
    glEnable(GL_LINE_SMOOTH);
    glHint(GL_LINE_SMOOTH_HINT, GL_NICEST);
    glEnable(GL_POLYGON_SMOOTH);
    glHint(GL_POLYGON_SMOOTH_HINT, GL_NICEST);

    /*
    ne::GLSLManager *pManager = pEngine->getGLSLManager();
    pProgram = pManager->create("Bump", "../shader/bump.glpg", true);
    ne::GLSLShaderPtr pVShader;
    pVShader = pProgram->createShaderFromFile("BumpVertexShader", "../shader/bump.glvs", ne::SHADER_TYPE_VERTEX);
    //ne::GLSLShaderPtr pGShader;
    //pGShader = pProgram->createShaderFromFile("BumpVertexShader", "../shader/bump.glgs", ne::SHADER_TYPE_GEOMETRY);
    ne::GLSLShaderPtr pFShader;
    pFShader = pProgram->createShaderFromFile("BumpFragmentShader", "../shader/bump.glfs", ne::SHADER_TYPE_FRAGMENT);
    pProgram->attachShader(pVShader);
    //pProgram->attachShader(pGShader);
    pProgram->attachShader(pFShader);
    pProgram->link();
    */

    // Set light
    light = new ne::Light();
    light->setType(ne::LT_POINT);
    light->setAmbient(ne::Color::GRAY);
    light->setAttenuationFactor(ne::LAT_LINEAR, 0.005f);
    light->setPosition(ne::Vector3(0.0f, 0.0f, 20.0f));

    /*
    ne::UIComponent *pUI = pEngine->getUIManager()->createUIComponent("UIButton", "Line Mode Button");
    pUI->setPosition(ne::Vector3(0.0f, 0.0f, 0.0f));
    pUI->setSize(100.0f, 20.0f);
    ne::UIButton *pButton = dynamic_cast<ne::UIButton*>(pUI);
    pButton->setText("Line Mode");
    pButton->setCallbackFunc(setLineMode);
    pButton->setColor(ne::Color(0.0f, 0.6f, 1.0f, 1.0f));
    pButton->setTextColor(ne::Color(1.0f, 1.0f, 1.0f));

    pUI = pEngine->getUIManager()->createUIComponent("UIButton", "Alpha Mode Button");
    pUI->setPosition(ne::Vector3(0.0f, 20.0f, 0.0f));
    pUI->setSize(100.0f, 20.0f);
    pButton = dynamic_cast<ne::UIButton*>(pUI);
    pButton->setText("Alpha Mode");
    pButton->setCallbackFunc(setAlphaMode);
    pButton->setColor(ne::Color(0.0f, 0.6f, 1.0f, 1.0f));
    pButton->setTextColor(ne::Color(1.0f, 1.0f, 1.0f));

    pUI = pEngine->getUIManager()->createUIComponent("UIButton", "Light Button");
    pUI->setPosition(ne::Vector3(0.0f, 40.0f, 0.0f));
    pUI->setSize(100.0f, 20.0f);
    pButton = dynamic_cast<ne::UIButton*>(pUI);
    pButton->setText("Light Off");
    pButton->setCallbackFunc(setLightOn);
    pButton->setColor(ne::Color(0.0f, 0.6f, 1.0f, 1.0f));
    pButton->setTextColor(ne::Color(1.0f, 1.0f, 1.0f));
    */
}

void keyboard(unsigned char key, int x, int y)
{
    if (key == 'w' || key == 'W')
    {
        camera->translate(ne::Vector3::NEGATIVE_UNIT_Z, ne::TS_LOCAL);
    }
    else if (key == 's' || key == 'S')
    {
        camera->translate(ne::Vector3::UNIT_Z, ne::TS_LOCAL);
    }
    else if (key == 'a' || key == 'A')
    {
        camera->translate(ne::Vector3::NEGATIVE_UNIT_X, ne::TS_LOCAL);
    }
    else if (key == 'd' || key == 'D')
    {
        camera->translate(ne::Vector3::UNIT_X, ne::TS_LOCAL);
    }
    else if (key == 'o' || key == 'O')
    {
        camera->translate(ne::Vector3::NEGATIVE_UNIT_Y, ne::TS_PARENT);
    }
    else if (key == 'u' || key == 'U')
    {
        camera->translate(ne::Vector3::UNIT_Y, ne::TS_PARENT);
    }
    else if (key == 'q' || key == 'Q')
    {
        camera->yaw(ne::Math::PI / 36);
    }
    else if (key == 'e' || key == 'E')
    {
        camera->yaw(- ne::Math::PI / 36);
    }
    else if (key == 'i' || key == 'I')
    {
        camera->pitch(ne::Math::PI / 36);
    }
    else if (key == 'k' || key == 'K')
    {
        camera->pitch(- ne::Math::PI / 36);
    }
    else if (key == 'j' || key == 'J')
    {
        camera->roll(ne::Math::PI / 36);
    }
    else if (key == 'l' || key == 'L')
    {
        camera->roll(- ne::Math::PI / 36);
    }
    else if (key == 'n' || key == 'N')
    {
        lineMode ^= 1;
    }
    else if (key == 'p' || key == 'P')
    {
        animationTimer.reset();
        playAnimation = true;
    }
    else if (key == 0x1b)
    {
        exit(1);
    }
}

void keyboardUp(unsigned char key, int x, int y)
{
}

void mouseFunc(int button, int state, int x, int y)
{
    if (button == GLUT_LEFT_BUTTON && state == GLUT_DOWN)
    {
        mousePosition.x = x;
        mousePosition.y = y;
        mouseOn = true;
    }
    else if (button == GLUT_LEFT_BUTTON && state == GLUT_UP)
    {
        mouseOn = false;
    }
}

void motionFunc(int x, int y)
{
    if (mouseOn)
    {
        float dx = x - mousePosition.x;
        float dy = y - mousePosition.y;
        ne::Quaternion q(ne::Vector3::UNIT_Y, 0.01f * dx);
        light->setPosition(q * light->getPosition());
        light->rotate(q, ne::TS_WORLD);

        q.fromAxisAngle(light->getRight(), 0.01f * dy);
        light->setPosition(q * light->getPosition());
        light->rotate(q, ne::TS_WORLD);
        mousePosition.x = x;
        mousePosition.y = y;
    }
}

void loop(void)
{
    // Update the Skeleton
    if (playAnimation && pSkeleton.notNull() && pAnimation.notNull())
    {
        ne::real nowTime = 0.001f * animationTimer.getMillisecondsCPU();
        pSkeleton->reset();
        pAnimation->applyToSkeleton(pSkeleton.ptr(), nowTime);
        if (nowTime > pAnimation->getLength())
        {
            playAnimation = false;
        }
    }
    if (pSkeleton.notNull() && pAnimation.notNull())
    {
        pSkeleton->update();
    }
    redraw();
}

void redraw(void)
{
    glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
    glMatrixMode(GL_MODELVIEW);
    const ne::Matrix4 &viewMatrix = camera->getViewMatrix();
    glLoadMatrixf(viewMatrix.ptr());

    if (lightOn)
    {
        //glEnable(GL_LIGHTING);
    }

    //glEnable(GL_COLOR_SUM);

    if (lineMode)
    {
        glPolygonMode(GL_FRONT_AND_BACK, GL_LINE);
    }
    else
    {
        glPolygonMode(GL_FRONT_AND_BACK, GL_FILL);
    }

    if (alphaMode)
    {
        ne::RenderSystem::GetSingleton().setDepthWriteEnable(false);
        glColor4f(0.0f, 0.5f, 1.0f, 0.5f);
    }
    else
    {
        ne::RenderSystem::GetSingleton().setDepthWriteEnable(true);
        glColor4f(0.5f, 0.5f, 0.5f, 1.0f);
    }

    glPushMatrix();
        if (pSkeleton.notNull())
        {
            const ne::JointVector &joints = pSkeleton->getJointVector();
            for (size_t i=0; i<joints.size(); ++i)
            {
                ne::Joint *pJoint = joints[i];
                glPushMatrix();
                    glColor4f(0.0f, 0.8f, 1.0f, 1.0f);
                    glMultMatrixf(pJoint->getTransformMatrix().ptr());
                    glutWireSphere(JointRadius, 8, 4);
                glPopMatrix();

                ne::Joint *pParent = dynamic_cast<ne::Joint*>(pJoint->getParent());
                if (pParent)
                {
                    glPushMatrix();
                        glMultMatrixf(pParent->getTransformMatrix().ptr());
                        ne::Vector3 line = pJoint->getPosition();
                        ne::real length = line.length();
                        ne::Vector3 endPoint = ((length - JointRadius) / length) * line;
                        ne::Quaternion q = ne::Quaternion::MakeRotation(ne::Vector3::UNIT_Z, line);
                        ne::Vector3 pl = q * ne::Vector3(-JointRadius, 0.0f, JointRadius);
                        ne::Vector3 pr = q * ne::Vector3(JointRadius, 0.0f, JointRadius);
                        ne::Vector3 pu = q * ne::Vector3(0.0f, JointRadius, JointRadius);
                        ne::Vector3 pd = q * ne::Vector3(0.0f, -JointRadius, JointRadius);
                        glColor4f(1.0f, 0.5f, 0.0f, 1.0f);
                        glBegin(GL_LINES);
                            glVertex3fv(pl.ptr());
                            glVertex3fv(endPoint.ptr());

                            glVertex3fv(pd.ptr());
                            glVertex3fv(endPoint.ptr());

                            glVertex3fv(pr.ptr());
                            glVertex3fv(endPoint.ptr());

                            glVertex3fv(pu.ptr());
                            glVertex3fv(endPoint.ptr());

                            glVertex3fv(pl.ptr());
                            glVertex3fv(pu.ptr());

                            glVertex3fv(pu.ptr());
                            glVertex3fv(pr.ptr());

                            glVertex3fv(pr.ptr());
                            glVertex3fv(pd.ptr());

                            glVertex3fv(pd.ptr());
                            glVertex3fv(pl.ptr());
                        glEnd();
                   glPopMatrix();
                }
            }
        }
    glPopMatrix();

    glPushMatrix();
        glColor4f(1.0f, 1.0f, 1.0f, 1.0f);
        ne::Vector3 pos = light->getPosition();
        glTranslatef(pos.x, pos.y, pos.z);
        glutSolidSphere(1.0f, 20, 20);
    glPopMatrix();

    glDisable(GL_COLOR_SUM);

    glDisable(GL_LIGHTING);

    glPolygonMode(GL_FRONT_AND_BACK, GL_FILL);

    drawFPS();

    glutSwapBuffers();
    glutPostRedisplay();
}

void resize(int width, int height)
{
    /* Save the new width and height */
    Width  = width;
    Height = height;

    /* Reset the viewport->.. */
    glViewport(0, 0, width, height);
    viewport->setPosition(0, 0);
    viewport->resize(width, height);

    glMatrixMode(GL_PROJECTION);
    const ne::Matrix4 &projectionMatrix = camera->getProjectionMatrix();
    glLoadMatrixf(projectionMatrix.ptr());
    //gluPerspective(45, double(width)/double(height), 1.0, 1000.0);
    glMatrixMode(GL_MODELVIEW);
}

void drawFPS()
{
    glClear(GL_DEPTH_BUFFER_BIT);

    //Set matrices for ortho
	glMatrixMode(GL_PROJECTION);
	glPushMatrix();
	glLoadIdentity();
	glOrtho(0.0f, viewport->getWidth(),
            viewport->getHeight(), 0.0f,
            1.0f, -1.0f);

	glMatrixMode(GL_MODELVIEW);
	glPushMatrix();
	glLoadIdentity();

	/*
    //Print text
    ne::Font *font = pEngine->getFont();
    ne::Vector3 pos(Width - 50.0f, 10.0f, 0.0f);
    float w = font->getFontWidth();
    float h = font->getFontHeight();

    char fpsString[32];
    ne::uint32_t interval = timer.getIntervalMilliseconds();
	sprintf(fpsString, "%.2f", 1000.0f / interval);

    font->renderText2D(fpsString, pos, 6.0f*w, h, ne::Color(0.8f, 1.0f, 0.0f, 1.0f));

    pEngine->getUIManager()->render();
    */

    //reset matrices
	glMatrixMode(GL_PROJECTION);
	glPopMatrix();
	glMatrixMode(GL_MODELVIEW);
	glPopMatrix();
}

